package engine;

import java.util.ArrayList;
import java.util.List;


import engine.Engine.EngineWebSocket;


public class Game {
	/**
	 * Ewok Poker
	 * By Viljar Kärgenberg
	 */
	final int PREFLOPGAME = 0;
	static final int FLOPGAME = 1;
	static final int TURNGAME = 2;
	static final int RIVERGAME = 3;
	final int SHOWDOWN = 4;
	static final int WINNER = 6;
	static final int SHOWDOWLENGTH = 5000;

	static final int FOLD = 0;
	static final int CHECK = 1;
	static final int CALL = 2;
	static final int RAISE = 3;
	static final int ALLIN = 4;
	static final int ALLINRAISE = 5;

	Player human;
	int NEXTPHASE = -1;
	static String[] PLAYERNAMES = { "Lobifo", "Ightend", "Yturu", "Keannper", "Calig",
		"Ight'gare", "War'os", "Rothildo", "Samther", "Garack", "Erk", "Oilde",
		"Tiaiv", "Bryine", "Sadala", "Yerorm", "Quel", "Thertornal",
		"Danworunt", "Emtor", "Ruich", "Navaty", "Alesay", "Mor'cer", "Dehes",
		"Tiathbel", "Ny'kin'eil", "Onndan", "Isnys", "Sudeny", "Tasage",
		"Turuvaro", "Orm'pere", "Ingu", "Pyphan", "Ineas", "Luiptur",
		"Ormoughack", "End'vor", "Essurn", "Imn", "Bansdel", "Emtas", "Darron",
		"Orrany", "Et'ser", "Bavesdan", "Haezkal", "Nozver", "Veban", "Rynique",
		"Alatair", "Dracula", "Frank N Stein", "J.J", "Ace", "Karl XII", "Yoda"};


	public void startGame() {
		this.init();
		ai = new AI(this);
		//set human player
		int randomNr = (int) Math.floor(Math.random()*this.players.size());
		this.players.get(randomNr).isHuman = true;
		this.players.get(randomNr).name = "You";
		this.players.get(randomNr).money = 100;
		human = this.players.get(randomNr);
		messenger.human = human;
		randomNr = (int) Math.floor(Math.random()*this.players.size());
		this.dealer = this.players.get(randomNr);
		for (int i = 0; i < this.players.size(); i++){
			//System.out.println(game.players[i].getPlayerHand());
		}
		this.start(PREFLOPGAME);
	}
	private List<Player> players;
	List<Player> currentPlayers;
	List<Player> currentPlayersUnordered;
	private List<Card> cCards;
	List<Player> allInPlayers;
	private Deck deck;
	int maxBets = 4;
	private Player dealer;
	private int smallBlind;
	private int bigBlind;
	int maxBetAmount;
	int[] playersBets;
	Player activePlayer;
	int pot;
	int betCounter;
	Player lastPlayerToAct;
	private Player sbPlayer;
	private Player bbPlayer;
	public AI ai;
	public Messenger messenger;
	public EngineWebSocket engine;
	public Game(EngineWebSocket engineWebSocket){
		this.players = new ArrayList<Player>();
		this.cCards = new ArrayList<Card>();//short for communityCards
		this.deck = new Deck();
		this.currentPlayers = new ArrayList<Player>();
		this.currentPlayersUnordered = new ArrayList<Player>();
		this.allInPlayers = new ArrayList<Player>();
		this.maxBets = 4;
		this.dealer = null;
		this.engine = engineWebSocket;
		this.smallBlind = 5;
		this.bigBlind = 10;
		this.maxBetAmount = this.bigBlind;
		this.pot = 0;
		this.betCounter = 0;
	}

	// meetod mängijate loomiseks
	private void init(){
		for (int i = 0; i < 9; i++){
			this.players.add(new Player(i,PLAYERNAMES[(int) Math.floor(Math.random()*PLAYERNAMES.length)],100,this));
			//this.players[i].isHuman = true;
		}
		this.playersBets = new int[this.players.size()];
	}

	// meetod boti tegevuste peatamiseks
	public void stop(){
		for ( int i = 0; i < this.players.size(); i++) { // peatame botid
			this.players.get(i).isHuman = true;
		}
	}
	// meetod mängufaasi alustamiseks
	public void start(int gamePhase){
		this.betCounter = 0;

		switch(gamePhase)
		{
		case (PREFLOPGAME):

			this.deck = new Deck(); 
		this.players = splice(this.players, this.players.indexOf(this.dealer)+1);
		this.currentPlayers = copy(this.players);
		this.currentPlayersUnordered = copy(this.players);
		this.lastPlayerToAct = this.currentPlayers.get(1);
		this.maxBetAmount = this.bigBlind;
		this.resetRoundBets();
		this.cCards = new ArrayList<Card>();
		this.currentPlayers = splice(this.currentPlayers, 2);
		this.activePlayer = this.currentPlayers.get(0);
		this.resetAllInPlayers();
		this.dealHands();
		System.out.println("");
		System.out.println("Preflop: " + "(Pot: " + (this.pot + this.smallBlind + this.bigBlind) + ")");
		this.addBlinds();
		NEXTPHASE = FLOPGAME;
		this.maxBets = 4;
		if (!this.activePlayer.isHuman){
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			ai.getAction(this.activePlayer);
		}


		break;

		case (FLOPGAME):
			synchronized(this.messenger){
				this.messenger.callAction("removeAllBets();");
			}
		this.currentPlayers = copy(this.currentPlayersUnordered);
		if (this.currentPlayers.size() > 0){
			this.activePlayer = this.currentPlayers.get(0);
		}
		this.lastPlayerToAct = this.currentPlayers.get(this.currentPlayers.size()-1);
		this.dealFlop();
		this.resetRoundBets();
		NEXTPHASE = TURNGAME;
		break;

		case (TURNGAME):
			synchronized(this.messenger){
				this.messenger.callAction("removeAllBets();");
			}
		this.currentPlayers = copy(this.currentPlayersUnordered);
		this.activePlayer = this.currentPlayers.get(0);
		this.lastPlayerToAct = this.currentPlayers.get(this.currentPlayers.size()-1);
		this.dealTurn();
		this.resetRoundBets();
		this.maxBetAmount = 2*this.bigBlind;
		NEXTPHASE = RIVERGAME;
		break;

		case (RIVERGAME):
			synchronized(this.messenger){
				this.messenger.callAction("removeAllBets();");
			}
			this.currentPlayers = copy(this.currentPlayersUnordered);
		if (this.currentPlayers.size() > 0){
			this.activePlayer = this.currentPlayers.get(0);
			this.lastPlayerToAct = this.currentPlayers.get(this.currentPlayers.size()-1);
		}
		this.dealRiver();
		this.resetRoundBets();
		this.maxBetAmount = 2*this.bigBlind;
		NEXTPHASE = SHOWDOWN;
		break;

		case (SHOWDOWN):
			ArrayList<Player> tmp = new ArrayList<Player>();
		tmp.addAll(this.currentPlayers);
		tmp.addAll(this.allInPlayers);
		synchronized(this.messenger){
			this.messenger.callAction(tmp, true, "showPlayerHands(players); removeAllBets();");
		}
		this.messenger.callAction("showPlayerButtons(false)");
		NEXTPHASE = PREFLOPGAME;
		Evaluator e = new Evaluator();
		e.evaluateHands(tmp, this.cCards);

		this.winnerIs(e.getWinners(tmp));
		break;

		}
		if (this.currentPlayers.size() > 0){
			this.updateButtons();
		}

	}


	private void dealHands(){
		for (int h = 0; h < 2; h++){
			for (int i = 0; i < this.players.size(); i++){
				this.players.get(i).hand.cards.add(this.deck.giveCard());
			}
		}
		System.out.println("");
		System.out.println("Table info: ");
		for (int i = 0; i < this.players.size(); i++){
			String info = "";
			if (this.players.get(i) == this.dealer){
				info = " Dealer";
			}
			if (this.players.get(i) == this.sbPlayer){
				info += " Small Blind";
			}
			if (this.players.get(i) == this.bbPlayer){
				info += " Big Blind";
			}
			System.out.println(this.players.get(i).name + ": " + this.players.get(i).money + "€; " + info + "\r\n" + this.players.get(i).getPlayerHand());
		}
		synchronized(this.messenger){
			this.messenger.callAction(this.players,false,"updatePlayers(players); updateActivePlayer(" + this.activePlayer.id + "); moveDealerButton(" + this.dealer.id + ");");
		}
		synchronized(this.messenger){
			this.messenger.callAction("setTimeout('animateDealingOfCards(0,1,players);',500)");
		}
	}

	private void dealFlop(){
		this.deck.giveCard(); // kaart maha
		System.out.println("");
		System.out.println("Flop: " + "(Pot: " + this.pot + ")");
		String cards = "";
		for ( int i = 0; i < 3; i++) {
			Card card = this.deck.giveCard();
			this.cCards.add(card);
			cards += card.getCardName() + " ";
		}
		System.out.println(cards);
		synchronized(this.messenger){
			this.messenger.callAction(this.cCards, "animateDealingOfFlop(cCards)");
		}
	}

	private void dealTurn(){
		this.deck.giveCard(); // kaart maha
		System.out.println("");
		System.out.println("Turn: " + "(Pot: " + this.pot + ")");
		Card card = this.deck.giveCard();
		this.cCards.add(card);
		System.out.println(card.getCardName());
		synchronized(this.messenger){
			this.messenger.callAction(this.cCards, "animateDealingOfTurn(cCards)");
		}
	}

	private void dealRiver(){
		this.deck.giveCard();// kaart maha
		System.out.println("");
		System.out.println("River: " + "(Pot: " + this.pot + ")");
		Card card = this.deck.giveCard();
		this.cCards.add(card);
		System.out.println(card.getCardName());
		synchronized(this.messenger){
			this.messenger.callAction(this.cCards, "animateDealingOfRiver(cCards)");
		}
	}

	private void addBlinds(){
		// adding small blind
		this.sbPlayer = this.players.get(0);
		this.bbPlayer = this.players.get(1);
		this.sbPlayer.bet = this.smallBlind;
		this.sbPlayer.money -= this.smallBlind;
		if (this.sbPlayer.money == 0){
			this.currentPlayers.remove(this.sbPlayer);
			this.currentPlayersUnordered.remove(this.sbPlayer);
			this.sbPlayer.goAllIn(this.smallBlind);
			this.activePlayer = this.currentPlayers.get(0); // juhuks, kui heads up ning SB läheb preflop blindiga ALLIN siis bb action
		}
		this.playersBets[this.sbPlayer.id] += this.smallBlind;
		this.pot += this.smallBlind;
		this.messenger.callAction(this.sbPlayer, this.smallBlind, "addPlayerBet(player,bet); updatePlayerDetails(player)");
		// adding big blind
		int bb;
		if (this.bbPlayer.money < this.bigBlind){
			bb = this.bbPlayer.money;
		}
		else {
			bb = this.bigBlind;
		}
		this.betCounter += 1;
		this.bbPlayer.bet = bb;
		this.bbPlayer.money -= bb;
		if (this.bbPlayer.money == 0){
			this.currentPlayers.remove(this.bbPlayer);
			this.currentPlayersUnordered.remove(this.bbPlayer);
			this.bbPlayer.goAllIn(bb);
			if (!this.sbPlayer.isAllIn){
				this.lastPlayerToAct = this.sbPlayer;
			}
			else {
				this.lastPlayerToAct = this.dealer;
			}
		}
		this.playersBets[this.bbPlayer.id] += bb;
		this.pot += bb;
		this.messenger.callAction(this.bbPlayer, bb, "addPlayerBet(player,bet); updatePlayerDetails(player)");
	}

	// meetod võitja kuvamiseks jms operatsioonide tegemiseks
	public void winnerIs(List<Player> winners){
		if (NEXTPHASE == PREFLOPGAME){
			// võitja tuli showdownist
			if (winners.size() == 1){
				winners.get(0).money += this.pot;
				String message = winners.get(0).name + " won the pot("+this.pot+"€) with " + winners.get(0).hand.getRank().toString();
				System.out.println(message);
				this.messenger.callAction("sendToChat(\"Dealer\",\"" + message+ "\")");
				winners.get(0).action = WINNER;
				this.messenger.callAction(winners.get(0),"updatePlayerDetails(player)");
			}
			else if (winners.size() > 1){
				// split pot
				// TODO: sidepotid
				String winnerNames = "";
				for ( int i = 0; i < winners.size(); i++) {
					winners.get(i).money += this.pot / winners.size();
					winners.get(i).action = WINNER;
					this.messenger.callAction(winners.get(0),"updatePlayerDetails(player)");
					winnerNames += winners.get(i).name + ", ";
				}
				String message = "Split pot between " + winnerNames + " each won ("+(this.pot / winners.size())+"€) with " + winners.get(0).hand.getRank().toString();
				System.out.println(message);
				this.messenger.callAction("sendToChat(\"Dealer\",\"" + message+ "\")");
			}
			else {
				//alert ("oh noes.");
			}
		}
		else{ //võitja ei ole showdownist
			String message = winners.get(0).name + " won the pot("+this.pot+"€)";
			this.messenger.callAction("sendToChat(\"Dealer\",\"" + message+ "\")");
			System.out.println(message);
			winners.get(0).action = WINNER;
			winners.get(0).money += this.pot;
			this.messenger.callAction(winners.get(0),"updatePlayerDetails(player)");
		}
		this.pot = 0;
		NEXTPHASE = PREFLOPGAME; //sest kui ilma showdownita kutsutakse meetod välja, siis vaja prefloppi järgmiseks roundiks
		//TODO:setTimeout("game.endRound()",SHOWDOWLENGTH );
		this.endRound();
		if (this.players.size() > 1){
			//TODO: setTimeout("game.start("+NEXTPHASE+")",SHOWDOWLENGTH+1000);
			this.start(NEXTPHASE);
			//TODO: this.updateUI;
		}
		else {
			this.messenger.callAction("alert('" + winners.get(0).name + " won!')");
			this.messenger.callAction("gameOver()");
			this.engine.endGame();
		}
	}

	private void resetRoundBets(){
		for ( int i = 0; i < this.playersBets.length; i++) {
			this.playersBets[i] = 0;
		}
	}
	// tagastab võimalikud käigud järjendina, et botid oskaksid otsustada
	public List<Integer> getAvailableActions(){
		List<Integer> actions = new ArrayList<Integer>();
		if (max(this.playersBets) - this.playersBets[this.activePlayer.id] > 0){
			actions.add(CALL);
		}
		else {
			actions.add(CHECK);
		}
		if (this.betCounter < 4 && (max(this.playersBets) + this.maxBetAmount - this.playersBets[this.activePlayer.id]) >= this.activePlayer.money){
			actions.add(ALLIN);
		}
		else{
			actions.add(RAISE);
		}
		if ((this.currentPlayers.size() == 1 && this.allInPlayers.size() >= 1) || this.betCounter >= 4){
			actions.remove((Object) RAISE);
		}
		if (actions.indexOf(CHECK) == -1){
			actions.add(FOLD);
		}
		if (actions.size() == 1  && NEXTPHASE != FLOPGAME){ 
			// mängija saab ainult checkida, st. mäng läheb showdowni
			// selleks, et botid ei saaks sel ajal enam tegutseda, lisame actions järjendisse -1
			actions = new ArrayList<Integer>();
			actions.add(-1);
		}
		return actions;
	}
	// uuenda mängijale kuvatavate nuppude labelid vastavalt võimalikele tegevustele
	public void updateButtons(){
		if (max(this.playersBets) - this.playersBets[this.activePlayer.id] > 0){
			this.messenger.callAction("changeCheckToCallButton()");
		}
		else {
			this.messenger.callAction("changeCallToCheckButton()");
		}
		if (max(this.playersBets) - this.playersBets[this.activePlayer.id] >= this.activePlayer.money){
			this.messenger.callAction("changeCallToAllInButton()");
		}
		if (this.betCounter < 4 && (max(this.playersBets) + this.maxBetAmount - this.playersBets[this.activePlayer.id]) >= this.activePlayer.money){
			this.messenger.callAction("changeRaiseToAllInButton()");
		} else {
			this.messenger.callAction("restoreRaiseButton()");
		}
		if (this.betCounter >= 4){
			this.messenger.callAction("disableRaiseButton()");
		}
		else {
			if (this.currentPlayers.size() == 1 && this.allInPlayers.size() >= 1){
				synchronized(this.messenger){
					this.messenger.callAction("disableRaiseButton()");
				}
			}
			else {
				this.messenger.callAction("enableRaiseButton()");
			}
		}

		this.messenger.callAction("showPlayerButtons("+this.activePlayer.isHuman+")");
	}

	public void resetAllInPlayers(){
		this.allInPlayers = new ArrayList<Player>();
		for ( int i = 0; i < this.players.size(); i++) {
			this.players.get(i).isAllIn = false;
		}
	}

	public void endRound(){
		// reset variables and do preparations for next hand
		List<Player> tmpPlayers = copy(this.players);
		String message = "";
		for (int i = 0; i < this.players.size(); i++) {
			this.players.get(i).hand = new Hand();
			Player tmpPlayer = this.players.get(i);
			tmpPlayer.action = -1;
			if (tmpPlayer.money == 0){ // eemaldame mängijad, kellel raha otsas
				tmpPlayers.remove(tmpPlayer);
				System.out.println(tmpPlayer.name + " is broke.");
				message += "sendToChat(\"Dealer\",\"" + tmpPlayer.name + " is broke.\"); ";
			}
		}
		while (messenger.actions.size() != 0){
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(6500);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		synchronized(this.messenger){
			this.messenger.callAction(tmpPlayers, false,"hidePlayersCards(players); removeAllBets(); hideCCards();" + message );
		}
		this.players = tmpPlayers;
		this.dealer = this.players.get(0);
	}

	public void updateUI(){
		if (this.currentPlayers.size() > 0){
			synchronized(this.messenger){
				this.messenger.callAction(this.activePlayer,"updatePlayerDetails(player); updateActivePlayer(player.id)");
			}
			this.updateButtons();
		}
	}
	public int max(int[] array){
		int m = 0;
		for (int i = 0; i < array.length; i++) {
			if (array[i] > m){
				m = array[i];
			}
		}
		return m;
	}
	public static List<Player> copy(List<Player> a){
		List<Player> b = new ArrayList<Player>();
		for (int i = 0; i < a.size(); i++) {
			b.add(a.get(i));
		}
		return b;
	}

	public static List <Player> splice(List<Player> a, int i){
		List <Player> tmp = new ArrayList <Player>();
		tmp = copy(a.subList(i, a.size()));
		tmp.addAll(a.subList(0,i));
		return tmp;
	}

	public void restoreState(){
		//TODO:this.messenger.callAction(this.players,false,"");
		/** kutsun players peal välja updateplayers
			currentPlayersi peal showhands
			this.playersBets peal hetke panused
			poti uuendamine
		 */
		List<Player> handPlayers = copy(currentPlayers);
		handPlayers.addAll(allInPlayers);
		String s = "updatePlayers(players); showPlayerCards(currentPlayers); addPlayerBets(currentPlayers,bets); updateActivePlayer(" + this.activePlayer.id + "); moveDealerButton(" + this.dealer.id + "); animateDealingOfCCards(cCards);";
		messenger.callRestore(this.players, handPlayers, this.cCards, this.playersBets, s);
	}

}